## @file
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# This program and the accompanying materials are licensed and made available
# under the terms and conditions of the BSD License which accompanies this
# distribution. The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

from plugins.EdkPlugins.basemodel import ini
from plugins.EdkPlugins.edk2.model import dsc
from plugins.EdkPlugins.edk2.model import inf
from plugins.EdkPlugins.edk2.model import dec
import os
from plugins.EdkPlugins.basemodel.message import *

class SurfaceObject(object):
    _objs = {}

    def __new__(cls, *args, **kwargs):
        """Maintain only a single instance of this object
        @return: instance of this class

        """
        obj = object.__new__(cls)
        if "None" not in cls._objs:
            cls._objs["None"] = []
        cls._objs["None"].append(obj)

        return obj

    def __init__(self, parent, workspace):
        self._parent    = parent
        self._fileObj   = None
        self._workspace = workspace
        self._isModify  = False
        self._modifiedObjs = []

    def __del__(self):
        pass

    def Destroy(self):
        key = self.GetRelativeFilename()
        self.GetFileObj().Destroy(self)
        del self._fileObj
        # dereference self from _objs arrary
        assert key in self._objs, "when destory, object is not in obj list"
        assert self in self._objs[key], "when destory, object is not in obj list"
        self._objs[key].remove(self)
        if len(self._objs[key]) == 0:
            del self._objs[key]

    def GetParent(self):
        return self._parent

    def GetWorkspace(self):
        return self._workspace

    def GetFileObjectClass(self):
        return ini.BaseINIFile

    def GetFilename(self):
        return self.GetFileObj().GetFilename()

    def GetFileObj(self):
        return self._fileObj

    def GetRelativeFilename(self):
        fullPath = self.GetFilename()
        return fullPath[len(self._workspace) + 1:]

    def Load(self, relativePath):
        # if has been loaded, directly return
        if self._fileObj is not None: return True

        relativePath = os.path.normpath(relativePath)
        fullPath = os.path.join(self._workspace, relativePath)
        fullPath = os.path.normpath(fullPath)

        if not os.path.exists(fullPath):
            ErrorMsg("file does not exist!", fullPath)
            return False

        self._fileObj = self.GetFileObjectClass()(fullPath, self)

        if not self._fileObj.Parse():
            ErrorMsg("Fail to parse file!", fullPath)
            return False

        # remove self from None list to list with filename as key
        cls = self.__class__
        if self not in cls._objs["None"]:
            ErrorMsg("Sufrace object does not be create into None list")
        cls._objs["None"].remove(self)
        if relativePath not in cls._objs:
            cls._objs[relativePath] = []
        cls._objs[relativePath].append(self)

        return True

    def Reload(self, force=False):
        ret = True
        # whether require must be update
        if force:
            ret = self.GetFileObj().Reload(True)
        else:
            if self.IsModified():
                if self.GetFileObj().IsModified():
                    ret = self.GetFileObj().Reload()
        return ret

    def Modify(self, modify=True, modifiedObj=None):
        if modify:
            #LogMsg("%s is modified, modified object is %s" % (self.GetFilename(), modifiedObj))
            if issubclass(modifiedObj.__class__, ini.BaseINIFile) and self._isModify:
                return
            self._isModify = modify
            self.GetParent().Modify(modify, self)
        else:
            self._isModify = modify

    def IsModified(self):
        return self._isModify

    def GetModifiedObjs(self):
        return self._modifiedObjs

    def FilterObjsByArch(self, objs, arch):
        arr = []
        for obj in objs:
            if obj.GetArch().lower() == 'common':
                arr.append(obj)
                continue
            if obj.GetArch().lower() == arch.lower():
                arr.append(obj)
                continue
        return arr

class Platform(SurfaceObject):
    def __init__(self, parent, workspace):
        SurfaceObject.__init__(self, parent, workspace)
        self._modules    = []
        self._packages   = []

    def Destroy(self):
        for module in self._modules:
            module.Destroy()
        del self._modules[:]

        del self._packages[:]
        SurfaceObject.Destroy(self)

    def GetName(self):
        return self.GetFileObj().GetDefine("PLATFORM_NAME")

    def GetFileObjectClass(self):
        return dsc.DSCFile

    def GetModuleCount(self):
        if self.GetFileObj() is None:
            ErrorMsg("Fail to get module count because DSC file has not been load!")

        return len(self.GetFileObj().GetComponents())

    def GetSupportArchs(self):
        return self.GetFileObj().GetDefine("SUPPORTED_ARCHITECTURES").strip().split('#')[0].split('|')

    def LoadModules(self, precallback=None, postcallback=None):
        for obj in self.GetFileObj().GetComponents():
            mFilename = obj.GetFilename()
            if precallback is not None:
                precallback(self, mFilename)
            arch = obj.GetArch()
            if arch.lower() == 'common':
                archarr = self.GetSupportArchs()
            else:
                archarr = [arch]
            for arch in archarr:
                module = Module(self, self.GetWorkspace())
                if module.Load(mFilename, arch, obj.GetOveridePcds(), obj.GetOverideLibs()):
                    self._modules.append(module)
                    if postcallback is not None:
                        postcallback(self, module)
                else:
                    del module
                    ErrorMsg("Fail to load module %s" % mFilename)

    def GetModules(self):
        return self._modules

    def GetLibraryPath(self, classname, arch, type):
        objs = self.GetFileObj().GetSectionObjectsByName("libraryclasses")

        for obj in objs:
            if classname.lower() != obj.GetClass().lower():
                continue
            if obj.GetArch().lower() != 'common' and \
               obj.GetArch().lower() != arch.lower():
                continue

            if obj.GetModuleType().lower() != 'common' and \
               obj.GetModuleType().lower() != type.lower():
                continue

            return obj.GetInstance()

        ErrorMsg("Fail to get library class %s [%s][%s] from platform %s" % (classname, arch, type, self.GetFilename()))
        return None

    def GetPackage(self, path):
        package = self.GetParent().GetPackage(path)
        if package not in self._packages:
            self._packages.append(package)
        return package

    def GetPcdBuildObjs(self, name, arch=None):
        arr = []
        objs = self.GetFileObj().GetSectionObjectsByName('pcds')
        for obj in objs:
            if obj.GetPcdName().lower() == name.lower():
                arr.append(obj)
        if arch is not None:
            arr = self.FilterObjsByArch(arr, arch)
        return arr

    def Reload(self, callback=None):
        # do not care force paramter for platform object
        isFileChanged = self.GetFileObj().IsModified()
        ret = SurfaceObject.Reload(self, False)
        if not ret: return False
        if isFileChanged:
            # destroy all modules and reload them again
            for obj in self._modules:
                obj.Destroy()
            del self._modules[:]
            del self._packages[:]
            self.LoadModules(callback)
        else:
            for obj in self._modules:
                callback(self, obj.GetFilename())
                obj.Reload()

        self.Modify(False)
        return True

    def Modify(self, modify=True, modifiedObj=None):
        if modify:
            #LogMsg("%s is modified, modified object is %s" % (self.GetFilename(), modifiedObj))
            if issubclass(modifiedObj.__class__, ini.BaseINIFile) and self._isModify:
                return
            self._isModify = modify
            self.GetParent().Modify(modify, self)
        else:
            if self.GetFileObj().IsModified():
                return
            for obj in self._modules:
                if obj.IsModified():
                    return

            self._isModify = modify
            self.GetParent().Modify(modify, self)

    def GetModuleObject(self, relativePath, arch):
        path = os.path.normpath(relativePath)
        for obj in self._modules:
            if obj.GetRelativeFilename() == path:
                if arch.lower() == 'common':
                    return obj
                if obj.GetArch() == arch:
                    return obj
        return None

    def GenerateFullReferenceDsc(self):
        oldDsc = self.GetFileObj()
        newDsc = dsc.DSCFile()
        newDsc.CopySectionsByName(oldDsc, 'defines')
        newDsc.CopySectionsByName(oldDsc, 'SkuIds')

        #
        # Dynamic common section should also be copied
        #
        newDsc.CopySectionsByName(oldDsc, 'PcdsDynamicDefault')
        newDsc.CopySectionsByName(oldDsc, 'PcdsDynamicHii')
        newDsc.CopySectionsByName(oldDsc, 'PcdsDynamicVpd')
        newDsc.CopySectionsByName(oldDsc, 'PcdsDynamicEx')

        sects = oldDsc.GetSectionByName('Components')
        for oldSect in sects:
            newSect = newDsc.AddNewSection(oldSect.GetName())
            for oldComObj in oldSect.GetObjects():
                module = self.GetModuleObject(oldComObj.GetFilename(), oldSect.GetArch())
                if module is None: continue

                newComObj = dsc.DSCComponentObject(newSect)
                newComObj.SetFilename(oldComObj.GetFilename())

                # add all library instance for override section
                libdict = module.GetLibraries()
                for libclass in libdict.keys():
                    if libdict[libclass] is not None:
                        newComObj.AddOverideLib(libclass, libdict[libclass].GetRelativeFilename().replace('\\', '/'))

                # add all pcds for override section
                pcddict = module.GetPcds()
                for pcd in pcddict.values():
                    buildPcd   = pcd.GetBuildObj()
                    buildType  = buildPcd.GetPcdType()
                    buildValue = None
                    if buildType.lower() == 'pcdsdynamichii' or \
                       buildType.lower() == 'pcdsdynamicvpd' or \
                       buildType.lower() == 'pcdsdynamicdefault':
                        buildType = 'PcdsDynamic'
                    if buildType != 'PcdsDynamic':
                        buildValue = buildPcd.GetPcdValue()
                    newComObj.AddOveridePcd(buildPcd.GetPcdName(),
                                            buildType,
                                            buildValue)
                newSect.AddObject(newComObj)
        return newDsc

class Module(SurfaceObject):
    def __init__(self, parent, workspace):
        SurfaceObject.__init__(self, parent, workspace)
        self._arch        = 'common'
        self._parent      = parent
        self._overidePcds = {}
        self._overideLibs = {}
        self._libs        = {}
        self._pcds        = {}
        self._ppis        = []
        self._protocols   = []
        self._depexs      = []
        self._guids       = []
        self._packages    = []

    def Destroy(self):
        for lib in self._libs.values():
            if lib is not None:
                lib.Destroy()
        self._libs.clear()

        for pcd in self._pcds.values():
            pcd.Destroy()
        self._pcds.clear()

        for ppi in self._ppis:
            ppi.DeRef(self)
        del self._ppis[:]

        for protocol in self._protocols:
            if protocol is not None:
                protocol.DeRef(self)
        del self._protocols[:]

        for guid in self._guids:
            if guid is not None:
                guid.DeRef(self)
        del self._guids[:]

        del self._packages[:]
        del self._depexs[:]
        SurfaceObject.Destroy(self)

    def GetFileObjectClass(self):
        return inf.INFFile

    def GetLibraries(self):
        return self._libs

    def Load(self, filename, arch='common', overidePcds=None, overideLibs=None):
        if not SurfaceObject.Load(self, filename):
            return False

        self._arch = arch
        if overidePcds is not None:
            self._overideLibs = overideLibs
        if overideLibs is not None:
            self._overidePcds = overidePcds

        self._SearchLibraries()
        self._SearchPackage()
        self._SearchSurfaceItems()
        return True

    def GetArch(self):
        return self._arch

    def GetModuleName(self):
        return self.GetFileObj().GetDefine("BASE_NAME")

    def GetModuleType(self):
        return self.GetFileObj().GetDefine("MODULE_TYPE")

    def GetPlatform(self):
        return self.GetParent()

    def GetModuleObj(self):
        return self

    def GetPcds(self):
        pcds = self._pcds.copy()
        for lib in self._libs.values():
            if lib is None: continue
            for name in lib._pcds.keys():
                pcds[name] = lib._pcds[name]
        return pcds

    def GetPpis(self):
        ppis = []
        ppis += self._ppis
        for lib in self._libs.values():
            if lib is None: continue
            ppis += lib._ppis
        return ppis

    def GetProtocols(self):
        pros = []
        pros = self._protocols
        for lib in self._libs.values():
            if lib is None: continue
            pros += lib._protocols
        return pros

    def GetGuids(self):
        guids = []
        guids += self._guids
        for lib in self._libs.values():
            if lib is None: continue
            guids += lib._guids
        return guids

    def GetDepexs(self):
        deps = []
        deps += self._depexs
        for lib in self._libs.values():
            if lib is None: continue
            deps += lib._depexs
        return deps

    def IsLibrary(self):
        return self.GetFileObj().GetDefine("LIBRARY_CLASS") is not None

    def GetLibraryInstance(self, classname, arch, type):
        if classname not in self._libs.keys():
            # find in overide lib firstly
            if classname in self._overideLibs.keys():
                self._libs[classname] = Library(self, self.GetWorkspace())
                self._libs[classname].Load(self._overideLibs[classname])
                return self._libs[classname]

            parent = self.GetParent()
            if issubclass(parent.__class__, Platform):
                path = parent.GetLibraryPath(classname, arch, type)
                if path is None:
                    ErrorMsg('Fail to get library instance for %s' % classname, self.GetFilename())
                    return None
                self._libs[classname] = Library(self, self.GetWorkspace())
                if not self._libs[classname].Load(path, self.GetArch()):
                    self._libs[classname] = None
            else:
                self._libs[classname] = parent.GetLibraryInstance(classname, arch, type)
        return self._libs[classname]

    def GetSourceObjs(self):
        return self.GetFileObj().GetSectionObjectsByName('source')

    def _SearchLibraries(self):
        objs = self.GetFileObj().GetSectionObjectsByName('libraryclasses')
        arch = self.GetArch()
        type = self.GetModuleType()
        for obj in objs:
            if obj.GetArch().lower() != 'common' and \
               obj.GetArch().lower() not in self.GetPlatform().GetSupportArchs():
                continue
            classname = obj.GetClass()
            instance = self.GetLibraryInstance(classname, arch, type)
            if not self.IsLibrary() and instance is not None:
                instance._isInherit = False

            if classname not in self._libs.keys():
                self._libs[classname] = instance

    def _SearchSurfaceItems(self):
        # get surface item from self's inf
        pcds  = []
        ppis  = []
        pros  = []
        deps  = []
        guids = []
        if self.GetFileObj() is not None:
            pcds = self.FilterObjsByArch(self.GetFileObj().GetSectionObjectsByName('pcd'),
                                          self.GetArch())
            for pcd in pcds:
                if pcd.GetPcdName() not in self._pcds.keys():
                    pcdItem = PcdItem(pcd.GetPcdName(), self, pcd)
                    self._pcds[pcd.GetPcdName()] = ModulePcd(self,
                                                             pcd.GetPcdName(),
                                                             pcd,
                                                             pcdItem)

            ppis += self.FilterObjsByArch(self.GetFileObj().GetSectionObjectsByName('ppis'),
                                          self.GetArch())

            for ppi in ppis:
                item = PpiItem(ppi.GetName(), self, ppi)
                if item not in self._ppis:
                    self._ppis.append(item)

            pros += self.FilterObjsByArch(self.GetFileObj().GetSectionObjectsByName('protocols'),
                                          self.GetArch())

            for pro in pros:
                item = ProtocolItem(pro.GetName(), self, pro)
                if item not in self._protocols:
                    self._protocols.append(item)

            deps += self.FilterObjsByArch(self.GetFileObj().GetSectionObjectsByName('depex'),
                                          self.GetArch())
            for dep in deps:
                item = DepexItem(self, dep)
                self._depexs.append(item)

            guids += self.FilterObjsByArch(self.GetFileObj().GetSectionObjectsByName('guids'),
                                          self.GetArch())
            for guid in guids:
                item = GuidItem(guid.GetName(), self, guid)
                if item not in self._guids:
                    self._guids.append(item)

    def _SearchPackage(self):
        objs = self.GetFileObj().GetSectionObjectsByName('packages')
        for obj in objs:
            package = self.GetPlatform().GetPackage(obj.GetPath())
            if package is not None:
                self._packages.append(package)

    def GetPackages(self):
        return self._packages

    def GetPcdObjects(self):
        if self.GetFileObj() is None:
            return []

        return self.GetFileObj().GetSectionObjectsByName('pcd')

    def GetLibraryClassHeaderFilePath(self):
        lcname = self.GetFileObj().GetProduceLibraryClass()
        if lcname is None: return None

        pkgs = self.GetPackages()
        for package in pkgs:
            path = package.GetLibraryClassHeaderPathByName(lcname)
            if path is not None:
                return os.path.realpath(os.path.join(package.GetFileObj().GetPackageRootPath(), path))
        return None

    def Reload(self, force=False, callback=None):
        if callback is not None:
            callback(self, "Starting reload...")

        ret = SurfaceObject.Reload(self, force)
        if not ret: return False

        if not force and not self.IsModified():
            return True

        for lib in self._libs.values():
            if lib is not None:
                lib.Destroy()
        self._libs.clear()

        for pcd in self._pcds.values():
            pcd.Destroy()
        self._pcds.clear()

        for ppi in self._ppis:
            ppi.DeRef(self)
        del self._ppis[:]

        for protocol in self._protocols:
            protocol.DeRef(self)
        del self._protocols[:]

        for guid in self._guids:
            guid.DeRef(self)
        del self._guids[:]

        del self._packages[:]
        del self._depexs[:]

        if callback is not None:
            callback(self, "Searching libraries...")
        self._SearchLibraries()
        if callback is not None:
            callback(self, "Searching packages...")
        self._SearchPackage()
        if callback is not None:
            callback(self, "Searching surface items...")
        self._SearchSurfaceItems()

        self.Modify(False)
        return True

    def Modify(self, modify=True, modifiedObj=None):
        if modify:
            #LogMsg("%s is modified, modified object is %s" % (self.GetFilename(), modifiedObj))
            if issubclass(modifiedObj.__class__, ini.BaseINIFile) and self._isModify:
                return
            self._isModify = modify
            self.GetParent().Modify(modify, self)
        else:
            if self.GetFileObj().IsModified():
                return

            self._isModify = modify
            self.GetParent().Modify(modify, self)

class Library(Module):
    def __init__(self, parent, workspace):
        Module.__init__(self, parent, workspace)
        self._isInherit = True

    def IsInherit(self):
        return self._isInherit

    def GetModuleType(self):
        return self.GetParent().GetModuleType()

    def GetPlatform(self):
        return self.GetParent().GetParent()

    def GetModuleObj(self):
        return self.GetParent()

    def GetArch(self):
        return self.GetParent().GetArch()

    def Destroy(self):
        self._libs.clear()
        self._pcds.clear()
        SurfaceObject.Destroy(self)

class Package(SurfaceObject):
    def __init__(self, parent, workspace):
        SurfaceObject.__init__(self, parent, workspace)
        self._pcds      = {}
        self._guids     = {}
        self._protocols = {}
        self._ppis      = {}

    def GetPcds(self):
        return self._pcds

    def GetPpis(self):
        return list(self._ppis.values())

    def GetProtocols(self):
        return list(self._protocols.values())

    def GetGuids(self):
        return list(self._guids.values())

    def Destroy(self):
        for pcd in self._pcds.values():
            if pcd is not None:
                pcd.Destroy()
        for guid in self._guids.values():
            if guid is not None:
                guid.Destroy()
        for protocol in self._protocols.values():
            if protocol is not None:
                protocol.Destroy()
        for ppi in self._ppis.values():
            if ppi is not None:
                ppi.Destroy()
        self._pcds.clear()
        self._guids.clear()
        self._protocols.clear()
        self._ppis.clear()
        self._pcds.clear()
        SurfaceObject.Destroy(self)

    def Load(self, relativePath):
        ret = SurfaceObject.Load(self, relativePath)
        if not ret: return False
        pcds = self.GetFileObj().GetSectionObjectsByName('pcds')
        for pcd in pcds:
            if pcd.GetPcdName() in self._pcds.keys():
                if self._pcds[pcd.GetPcdName()] is not None:
                    self._pcds[pcd.GetPcdName()].AddDecObj(pcd)
            else:
                self._pcds[pcd.GetPcdName()] = PcdItem(pcd.GetPcdName(), self, pcd)

        guids = self.GetFileObj().GetSectionObjectsByName('guids')
        for guid in guids:
            if guid.GetName() not in self._guids.keys():
                self._guids[guid.GetName()] = GuidItem(guid.GetName(), self, guid)
            else:
                WarnMsg("Duplicate definition for %s" % guid.GetName())

        ppis = self.GetFileObj().GetSectionObjectsByName('ppis')
        for ppi in ppis:
            if ppi.GetName() not in self._ppis.keys():
                self._ppis[ppi.GetName()] = PpiItem(ppi.GetName(), self, ppi)
            else:
                WarnMsg("Duplicate definition for %s" % ppi.GetName())

        protocols = self.GetFileObj().GetSectionObjectsByName('protocols')
        for protocol in protocols:
            if protocol.GetName() not in self._protocols.keys():
                self._protocols[protocol.GetName()] = ProtocolItem(protocol.GetName(), self, protocol)
            else:
                WarnMsg("Duplicate definition for %s" % protocol.GetName())

        return True

    def GetFileObjectClass(self):
        return dec.DECFile

    def GetName(self):
        return self.GetFileObj().GetDefine("PACKAGE_NAME")

    def GetPcdDefineObjs(self, name=None):
        arr = []
        objs = self.GetFileObj().GetSectionObjectsByName('pcds')
        if name is None: return objs

        for obj in objs:
            if obj.GetPcdName().lower() == name.lower():
                arr.append(obj)
        return arr

    def GetLibraryClassObjs(self):
        return self.GetFileObj().GetSectionObjectsByName('libraryclasses')

    def Modify(self, modify=True, modifiedObj=None):
        if modify:
            self._isModify = modify
            self.GetParent().Modify(modify, self)
        else:
            if self.GetFileObj().IsModified():
                return

            self._isModify = modify
            self.GetParent().Modify(modify, self)

    def GetLibraryClassHeaderPathByName(self, clsname):
        objs = self.GetLibraryClassObjs()
        for obj in objs:
            if obj.GetClassName() == clsname:
                return obj.GetHeaderFile()
        return None

class DepexItem(object):
    def __init__(self, parent, infObj):
        self._parent = parent
        self._infObj = infObj

    def GetDepexString(self):
        return str(self._infObj)

    def GetInfObject(self):
        return self._infObj

class ModulePcd(object):
    _type_mapping = {'FeaturePcd': 'PcdsFeatureFlag',
                     'FixedPcd': 'PcdsFixedAtBuild',
                     'PatchPcd': 'PcdsPatchableInModule'}

    def __init__(self, parent, name, infObj, pcdItem):
        assert issubclass(parent.__class__, Module), "Module's PCD's parent must be module!"
        assert pcdItem is not None, 'Pcd %s does not in some package!' % name

        self._name          = name
        self._parent        = parent
        self._pcdItem       = pcdItem
        self._infObj        = infObj

    def GetName(self):
        return self._name

    def GetParent(self):
        return self._name

    def GetArch(self):
        return self._parent.GetArch()

    def Destroy(self):
        self._pcdItem.DeRef(self._parent)
        self._infObj = None

    def GetBuildObj(self):
        platformInfos = self._parent.GetPlatform().GetPcdBuildObjs(self._name, self.GetArch())
        modulePcdType = self._infObj.GetPcdType()

        # if platform do not gives pcd's value, get default value from package
        if len(platformInfos) == 0:
            if modulePcdType.lower() == 'pcd':
                return self._pcdItem.GetDecObject()
            else:
                for obj in self._pcdItem.GetDecObjects():
                    if modulePcdType not in self._type_mapping.keys():
                        ErrorMsg("Invalid PCD type %s" % modulePcdType)
                        return None

                    if self._type_mapping[modulePcdType] == obj.GetPcdType():
                        return obj
                ErrorMsg ('Module PCD type %s does not in valied range [%s] in package!' % \
                          (modulePcdType))
        else:
            if modulePcdType.lower() == 'pcd':
                if len(platformInfos) > 1:
                    WarnMsg("Find more than one value for PCD %s in platform %s" % \
                            (self._name, self._parent.GetPlatform().GetFilename()))
                return platformInfos[0]
            else:
                for obj in platformInfos:
                    if modulePcdType not in self._type_mapping.keys():
                        ErrorMsg("Invalid PCD type %s" % modulePcdType)
                        return None

                    if self._type_mapping[modulePcdType] == obj.GetPcdType():
                        return obj

                ErrorMsg('Can not find value for pcd %s in pcd type %s' % \
                         (self._name, modulePcdType))
        return None


class SurfaceItem(object):
    _objs = {}

    def __new__(cls, *args, **kwargs):
        """Maintain only a single instance of this object
        @return: instance of this class

        """
        name    = args[0]
        parent  = args[1]
        fileObj = args[2]
        if issubclass(parent.__class__, Package):
            if name in cls._objs.keys():
                ErrorMsg("%s item is duplicated defined in packages: %s and %s" %
                         (name, parent.GetFilename(), cls._objs[name].GetParent().GetFilename()))
                return None
            obj = object.__new__(cls)
            cls._objs[name] = obj
            return obj
        elif issubclass(parent.__class__, Module):
            if name not in cls._objs.keys():
                ErrorMsg("%s item does not defined in any package! It is used by module %s" % \
                         (name, parent.GetFilename()))
                return None
            return cls._objs[name]

        return None


    def __init__(self, name, parent, fileObj):
        if issubclass(parent.__class__, Package):
            self._name    = name
            self._parent  = parent
            self._decObj  = [fileObj]
            self._refMods = {}
        else:
            self.RefModule(parent, fileObj)

    @classmethod
    def GetObjectDict(cls):
        return cls._objs

    def GetParent(self):
        return self._parent

    def GetReference(self):
        return self._refMods

    def RefModule(self, mObj, infObj):
        if mObj in self._refMods.keys():
            return
        self._refMods[mObj] = infObj

    def DeRef(self, mObj):
        if mObj not in self._refMods.keys():
            WarnMsg("%s is not referenced by module %s" % (self._name, mObj.GetFilename()))
            return
        del self._refMods[mObj]

    def Destroy(self):
        self._refMods.clear()
        cls = self.__class__
        del cls._objs[self._name]

    def GetName(self):
        return self._name

    def GetDecObject(self):
        return self._decObj[0]

    def GetDecObjects(self):
        return self._decObj

class PcdItem(SurfaceItem):
    def AddDecObj(self, fileObj):
        for decObj in self._decObj:
            if decObj.GetFilename() != fileObj.GetFilename():
                ErrorMsg("Pcd %s defined in more than one packages : %s and %s" % \
                         (self._name, decObj.GetFilename(), fileObj.GetFilename()))
                return
            if decObj.GetPcdType() == fileObj.GetPcdType() and \
               decObj.GetArch().lower() == fileObj.GetArch():
                ErrorMsg("Pcd %s is duplicated defined in pcd type %s in package %s" % \
                         (self._name, decObj.GetPcdType(), decObj.GetFilename()))
                return
        self._decObj.append(fileObj)

    def GetValidPcdType(self):
        types = []
        for obj in self._decObj:
            if obj.GetPcdType() not in types:
                types += obj.GetPcdType()
        return types

class GuidItem(SurfaceItem):
    pass

class PpiItem(SurfaceItem):
    pass

class ProtocolItem(SurfaceItem):
    pass
